package com.abel.bigwater.tlv.server

import io.netty.bootstrap.Bootstrap
import io.netty.bootstrap.ServerBootstrap
import io.netty.channel.ChannelInitializer
import io.netty.channel.ChannelOption
import io.netty.channel.nio.NioEventLoopGroup
import io.netty.channel.socket.SocketChannel
import io.netty.channel.socket.nio.NioDatagramChannel
import io.netty.channel.socket.nio.NioServerSocketChannel
import io.netty.handler.timeout.IdleStateHandler
import org.slf4j.LoggerFactory

object TlvServer {
    private val DEFAULT_WLMR_PORT = 9701

    private val logger = LoggerFactory.getLogger(TlvServer::class.java)

    private val usage = """Usage (20180323.1):\njava -jar tlv-server-1.0.jar [port]
        port - port number to listen on, default to ${DEFAULT_WLMR_PORT}."""

    @Throws(Exception::class)
    @JvmStatic
    fun main(args: Array<String>) {
        println(usage)
        TlvServer.run(*args)
    }

    @Throws(Exception::class)
    fun run(vararg args: String) {
        // start up socket server on certain port.
        val port: Int
        if (args.size > 0) {
            port = Integer.parseInt(args[0])
        } else {
            port = DEFAULT_WLMR_PORT
        }

        logger.info("Launching TLV Server On $port ...")

        // UDP
        Thread(Runnable {
            try {
                logger.info("TLV UDP server launching on port : $port")
                launchWlmrUdpServer(port)
                logger.info("TLV UDP server exited normally.")
            } catch (e: Exception) {
                logger.info("TLV UDP server exited caused by " + e.message, e)
            }
        }).start()

        launchWlmrTcpServer(port)
    }

    /**
     * @param port
     * @throws InterruptedException
     */
    @Throws(InterruptedException::class)
    fun launchWlmrTcpServer(port: Int) {
        val bossGroup = NioEventLoopGroup() // (1)
        val workerGroup = NioEventLoopGroup()
        try {
            val b = ServerBootstrap() // (2)
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel::class.java) // (3)
                    .childHandler(object : ChannelInitializer<SocketChannel>() { // (4)
                        @Throws(Exception::class)
                        public override fun initChannel(ch: SocketChannel) {
                            ch.pipeline().addLast(
                                    IdleStateHandler(2, 3, 4),
                                    TlvPacketDecoder(),
                                    TlvServerHandler())
                        }
                    })
                    .option(ChannelOption.SO_BACKLOG, 128) // (5)
                    .childOption(ChannelOption.SO_KEEPALIVE, true) // (6)

            // Bind and start to accept incoming connections.
            val f = b.bind(port).sync() // (7)
            logger.info("Bind to specified port - $port")

            // Wait until the server socket is closed.
            // In this example, this does not happen, but you can do that to
            // gracefully
            // shut down your server.
            f.channel().closeFuture().sync()
            logger.error("Server exits suprisingly!")
        } finally {
            logger.error("Clean up before server exit.")
            workerGroup.shutdownGracefully()
            bossGroup.shutdownGracefully()
        }
    }

    @Throws(Exception::class)
    private fun launchWlmrUdpServer(port: Int) {
        val b = Bootstrap()
        val group = NioEventLoopGroup()
        b.group(group)
                .channel(NioDatagramChannel::class.java)
                .option(ChannelOption.SO_BROADCAST, true)
                .handler(TlvServerHandler())

        b.bind(port).sync().channel().closeFuture().await()
    }
}